# !/usr/bin/env python3
# -*- coding:utf-8 -*-
"""
定义棋盘及相关操作
"""
import os
import json
import pathlib

import numpy as np

import zobrist
from util import Chess, cases, Score


class ChessBoard(object):
    """定义棋盘类及关联的操作"""
    def __init__(self, size:int):
        """初始化
        Args:
            size: 初始化board的大小
        """
        self.size = size
        self.board = np.zeros((size, size), dtype=np.int8)
        self.wscore = np.zeros((size, size), dtype=np.float32)
        self.bscore = np.zeros((size, size), dtype=np.float32)
        self.history = []
        self.current = Chess.WHITE
        self._zobrist = zobrist.Zobrist(size)

    def reverse(self):
        """反转棋子"""
        self.board *= -1
        self.current *= -1
        self._zobrist.reverse()
        return True

    def is_full(self):
        """是否已满"""
        return len(self.history) >= self.size ** 2

    def go(self, row: int, col: int):
        """自动交替"""
        assert self.board[row, col] == Chess.EMPTY, ValueError(f"Position ({row}, {col}) not empty!")
        self.current *= -1
        self.board[row, col] = self.current
        self.history.append((row, col))
        zobrist_hash = self._zobrist.go(row, col, self.current)
        return zobrist_hash

    def back(self):
        """回退一步"""
        if not self.history:
            return False
        self.current *= -1
        row, col = self.history.pop()
        self._zobrist.go(row, col, self.current)
        self.board[row, col] = Chess.EMPTY
        return (row, col)

    def reset(self):
        """重置棋盘"""
        self.current = Chess.WHITE
        self.board *= Chess.EMPTY
        self.history.clear()
        self._zobrist = zobrist.Zobrist(self.size)
        return True

    def _trans(self, val: int):
        """转换表示"""
        if val == Chess.EMPTY:
            return "-"
        if val == Chess.BLACK:
            return "x"
        return "o"

    def show(self, focus_n:int=1):
        """display matrix readable"""
        print("")
        print("  " + "  ".join(hex(i).upper().replace("0X", "") for i in range(self.size)))
        focus_points = self.history[-focus_n:]
        for i in range(self.size):
            row_list = [("  " + self._trans(v)) for v in self.board[i]]
            for row, col in focus_points:
                if i == row:
                    row_list[col] = f' [{self._trans(self.board[i, col])}]'
                    if col < self.size - 1:
                        row_list[col+1] = f' {self._trans(self.board[i, col+1])}'
            row_str = "".join(row_list).strip()
            print(hex(i).upper().replace("0X", "") + ('' if row_str.startswith('[') else ' ') + row_str)

    def load(self, file_path: str):
        """加载数据"""
        assert os.path.isfile(file_path), "Not valid file path!"
        with open(file_path) as fp:
            data = json.load(fp)
        self.board = np.array(data["chessboard"], dtype=np.int8)
        self.size = self.board.shape[0]
        self.history = data["history"]
        self.current = Chess.WHITE
        if self.history:
            self.current = self.board[self.history[-1][0], self.history[-1][1]]

    def save(self, file_path: str):
        """保存数据"""
        path = pathlib.Path(file_path)
        if not path.parent.is_dir():
            os.makedirs(path.parent)
        with open(file_path, "w") as fp:
            data = {"chessboard": self.board.tolist(), "history": self.history}
            json.dump(data, fp)

    def _search_case(self, chars: str)->float:
        """搜索序列是否存在"""
        rchars = chars[::-1]
        same_flag = (rchars == chars)
        for case, score in cases:
            idx = chars.find(case)
            if idx != -1:
                return score
            if not same_flag:
                idx = rchars.find(case)
                if idx != -1:
                    return score
        return 0.0

    def _evaluate(self) -> float:
        """评分"""
        sz = self.size
        min_len = 4 # 序列最短长度
        row, col = self.history[-1]
        scores = [1.0, 1.0, 1.0, 1.0]
        # 竖直方向查找
        rs, re = max(0, row-5), min(row+6, sz)
        hori_str = ''.join(map(str, (self.board[r, col] for r in range(rs, re))))
        if row - 5 < 0:
            hori_str = 'd' + hori_str
        if row + 6 > sz:
            hori_str = hori_str + 'd'
        scores[0] = self._search_case(hori_str)
        if scores[0] >= Score.LIVE4:
            self.bscore[row, col] = scores[0]
            return scores[0]
        # 水平方向查找
        cs, ce = max(0, col-5), min(col+6, sz)
        vert_str = ''.join(map(str, (self.board[c, row] for c in range(cs, ce))))
        if col - 5 < 0:
            vert_str = 'd' + vert_str
        if col + 6 > sz:
            vert_str = vert_str + 'd'
        scores[1] = self._search_case(vert_str)
        if scores[1] >= Score.LIVE4:
            self.bscore[row, cs:ce] = scores[1]
            return scores[1]
        # 对角线方向
        offset = col - row # 第几条对角线取决于偏移量
        idx = min(row, col) # 元素在对角线数组中的序号
        ds, de = max(0, idx-5), min(sz, idx+6)
        if sz - abs(offset) >= min_len: 
            diag = np.diagonal(self.board, offset=offset)
            diag_str = ''.join(map(str, (diag[i] for i in range(ds, de))))
            if idx - 5 < 0:
                diag_str = 'd' + diag_str
            if idx + 6 > sz:
                diag_str = diag_str + 'd'
            scores[2] = self._search_case(diag_str)
            if scores[2] >= Score.LIVE4:
                ir = np.array(range(sz-abs(offset)))
                ic = ir + abs(offset)
                if offset < 0:
                    ir, ic = ic, ir
                self.bscore[ir, ic] = scores[2]
                return scores[2]
        # 反对角线方向
        # 反转原矩阵使用
        row = sz - 1 - row
        col = sz - 1 - col
        offset = row - col # 第几条对角线取决于偏移量
        idx = min(row, col) # 元素在对角线数组中的序号
        ds, de = max(0, idx-5), min(sz, idx+6)
        if sz - abs(offset) >= min_len:
            rdiag = np.diagonal(np.fliplr(self.board), offset=offset)
            rdiag_str = ''.join(map(str, (rdiag[i] for i in range(ds, de))))
            if idx - 5 < 0:
                rdiag_str = 'd' + rdiag_str
            if idx + 6 > sz:
                rdiag_str = rdiag_str + 'd'
            scores[3] = self._search_case(rdiag_str)
            if scores[3] >= Score.LIVE4:
                ir = np.array(range(sz-abs(offset)))
                ic = ir + abs(offset)
                if offset < 0:
                    ir, ic = ic, ir
                return scores[3]
        # 如果同时存在两种情况，则取最大的两者返回乘积
        scores.sort()
        return scores[-2] * scores[-1]
